import java.net.*;
import java.io.*;
import java.util.*;

public class ChatClient implements ApplicationListener{
    private ChatClientRecord clientRec;
    private BroadcastChannel chan;

    public ChatClient(String serverName, int serverPort, int clientPort, boolean fifo) {
        // Prompt user for name
        Scanner scan = new Scanner(System.in);
        System.out.println("Please input your name:");
        String clientName = scan.nextLine();

        Socket serverSocket = null;
        ObjectOutputStream serverOut = null;
        ObjectInputStream serverIn = null;

        try {
            // Attempt to connect to server
            serverSocket = new Socket(serverName, serverPort);
            serverOut = new ObjectOutputStream(serverSocket.getOutputStream());
            serverIn = new ObjectInputStream(serverSocket.getInputStream());

            // Send registration request
            this.clientRec = new ChatClientRecord(clientName, InetAddress.getLocalHost().getHostName(), clientPort);
            ChatMessage regRequest = new ChatMessage(ChatMessage.REG_ATTEMPT, null, null, clientRec);
            serverOut.writeObject(regRequest);

            // If registration is successful, start listening for other client connections and
            // user attempts to connect to other clients
            ChatMessage regResponse = (ChatMessage)serverIn.readObject();

            serverSocket.close();
            serverOut.close();
            serverIn.close();

            if (regResponse.type == ChatMessage.REG_SUCCESS) {
                System.out.println("Registration success!"); 

                // Start sending hearbeats
                TimerTask heartbeatSender = new HeartbeatSender(serverName, serverPort); 
                Timer heartbeatTimer = new Timer();
                heartbeatTimer.schedule(heartbeatSender, 30*1000, 30*1000);

                // Start listening for client connections
                if(fifo){
                    chan = new FIFOReliableChannel(clientRec,this);
                }else{
                    chan = new ReliableChannel(clientRec,this);
                }

                // Register everyone in the chatroom to the ReliableChannel
                for(ChatClientRecord rec : regResponse.groupList)
                    chan.addMember(rec);

                // Read commands from stdin
                Scanner stdin = new Scanner(System.in);
                while (true) {
                    String[] cmdParts = stdin.nextLine().split(" ");
                    String cmd = cmdParts[0];

                    //execute command
                    if (cmd.equals("exit")) {
                        // Quit the chat client
                        // Get a list of connected clients from the server
                        // open traditional TCP/IP connection to server
                        serverSocket = new Socket(serverName, serverPort);
                        serverOut = new ObjectOutputStream(serverSocket.getOutputStream());

                        //send request to server
                        ChatMessage msg = new ChatMessage(ChatMessage.REG_LEAVE, null, null, this.clientRec);
                        serverOut.writeObject(msg);

                        //close connections
                        serverSocket.close();
                        serverOut.close();
                        break;
                    } else if (cmd.equals("list")) {
                        // Get a list of connected clients from the server
                        // open traditional TCP/IP connection to server
                        serverSocket = new Socket(serverName, serverPort);
                        serverOut = new ObjectOutputStream(serverSocket.getOutputStream());
                        serverIn = new ObjectInputStream(serverSocket.getInputStream());
                        
                        //send request to server
                        ChatMessage msg = new ChatMessage(ChatMessage.GET_LIST, null, null, this.clientRec);
                        serverOut.writeObject(msg);
                        //read response
                        ChatMessage response = (ChatMessage)serverIn.readObject();

                        //process response
                        receive(response);

                        //close connections
                        serverSocket.close();
                        serverOut.close();
                        serverIn.close();
                    } else if(cmd.equals("chat")){
                        if(cmdParts.length > 1){
                            //concatenate the parts of the chat message back into a single string
                            String message = cmdParts[1];
                            for (int i = 2; i < cmdParts.length; i++)
                                message += (" " + cmdParts[i]);             
                            //broadcast the message
                            chan.sendMessage(new ChatMessage(ChatMessage.IM, message, null, clientRec));
                        }else{
                            System.err.println("Usage: chat <message>");
                        }
                    } else {
                        //Print available commands
                        System.out.println("Commands:\n\tlist\n\tchat <message>\n\texit");
                    }
                }

                //Exit when we're done looping. (break from 'exit' command)
                System.exit(1);
            } else if (regResponse.type == ChatMessage.REG_FAIL) {
                System.err.println("Registration failure."); 
                System.exit(1);
            }
        } catch (IOException e) {
            System.err.println("Error: "+e.getMessage());
            System.exit(1);
        } catch (ClassNotFoundException e) {
            System.err.println("Could not translate registration response to a class");
            System.exit(1);
        }
    }

    private class HeartbeatSender extends TimerTask {
        private String serverName;
        private int serverPort;

        public HeartbeatSender(String serverName, int serverPort) {
            this.serverName = serverName;
            this.serverPort = serverPort;
        }

        // Send heartbeat to server
        public void run() {
            try {
                Socket sock = new Socket(serverName, serverPort);    
                ObjectOutputStream out = new ObjectOutputStream(sock.getOutputStream());
                out.writeObject(new ChatMessage(ChatMessage.HEARTBEAT, null, null, clientRec));
                out.close();
                sock.close();
            } catch (UnknownHostException e) {
                System.err.println("Could not connect to " + serverName + ":" + serverPort);
                System.exit(1);
            } catch (IOException e) {
                System.err.println("Could not send heartbeat");
                System.exit(1);
            }
        }
    }

    public void receive(ChatMessage m){
        if(m.type == ChatMessage.IM){
            System.out.println(m.clientRec.name+">"+m.payload);
        }else if(m.type == ChatMessage.GET_LIST){
            //add members to the channel (if the member is already in the channel it does nothing)
            for(int i = 0; i < m.groupList.size(); i++){
                ChatClientRecord rec = m.groupList.get(i);
                chan.addMember(rec);
            }
            //remove members which are in the channel but not in the server's returned group
            ArrayList<ChatClientRecord> channelGroup = chan.getMembers();
            for(int i = 0; i < channelGroup.size(); i++){
                ChatClientRecord rec = channelGroup.get(i);
                if(!m.groupList.contains(rec)){
                    chan.removeMember(rec);
                }
            }

            if(!m.clientRec.name.equals("__server")){//only print if the response wasn't originated by the server
                //print list
                channelGroup = chan.getMembers();//refresh the members
                System.out.println("Connected clients:");
                for (ChatClientRecord rec : channelGroup) {
                    if(!rec.name.equals("__server")){//don't print out the server's record
                        //print entry
                        System.out.println(rec.name + (rec.equals(this.clientRec)?"(me) -> ":" -> ") + rec.ip+":"+rec.port);
                    }
                }
            }
        }else{
            System.out.println("Unknown message");
        }
    }

    public static void main(String[] args) {
        String serverName;
        int serverPort;
        int clientPort;
        if (args.length == 4) {
            serverName = args[0];
            serverPort = Integer.parseInt(args[1]);
            clientPort = Integer.parseInt(args[2]);
            boolean fifo = args[3].equals("fifo");
            new ChatClient(serverName, serverPort, clientPort, fifo);
        } else {
            System.out.println("Usage: java ChatClient hostName hostPort clientPort <reliable|fifo>");
        }
    }
}
